/*
 * OpenParts
 * A dynamic-loading components framework for GWT
 * Copyright (C) 2011 Christophe Bouthier  [chris{AT}binary-gastronome{DOT}fr]
 *
 * This work is partially based on work I have done at INRIA (http://www.inria.fr) 
 * in the context of the Qualipso European Project (http://qualipso.org/),
 * The work done at INRIA is Copyright (C) 2006-2011 INRIA
 *
 * This work is distributed under the LGPL version 3
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License version 3
 * as published by the Free Software Foundation. See the GNU
 * Lesser General Public License in LGPL.txt for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package fr.openparts.OpenParts.client;

/**
 * OpenParts shell class, allowing dynamic loading of parts, communication between them, and loading of views. Similar to a toolbox manager in System 7, this is
 * the main facade class for interaction between parts.
 * <p>
 * A part is a GWT module compiled with the OpenParts support. This allow the module to be loaded dynamically, to communicate with other parts through an event
 * bus, and to load dynamically views of other parts.
 * <p>
 * OpenParts support is done by making the GWT module defining the part inherits the OpenParts module, by adding the following line to its definition :
 * <pre>
 *     &lt;inherits name='fr.openparts.OpenParts.OpenParts' /&gt;
 * </pre>
 * 
 * In order to communicate with other parts, a part should defines specific annotations.
 * 
 * @author Christophe Bouthier [chris{AT}binary-gastronome{DOT}fr]
 * @creation.date 3 December 2009
 */
public final class OPShell {

    /**
     * Suffix of a part loader javascript file. Value is ".nocache.js". Used by {@link #partLoaderUrlForService(String)}.
     */
    public static final String PART_LOADER_SUFFIX = ".nocache.js";

    /**
     * Prefix of ID for dynamically loaded script. Value is "op_loaded_script_". Used by {@link #partLoaderIdForService(String)}.
     */
    public static final String LOADED_SCRIPT_ID_PREFIX = "op_loaded_script_";

    /**
     * Standard view name for a root view, displayed in the whole page.
     */
    public static final String ACTION_ROOT = "root";

    /**
     * Standard view name for a resource creation form.
     */
    public static final String ACTION_CREATE = "create";

    /**
     * Standard view name for a resource display view.
     */
    public static final String ACTION_DISPLAY = "display";

    /**
     * Standard view name for a resources list view.
     */
    public static final String ACTION_LIST = "list";

    /**
     * Special value of slot id to display the view as the root panel of the page.
     */
    public static final String SLOT_ID_ROOT = "slotidroot";

    /**
     * Private constructor as it is an utility class.
     */
    private OPShell() {}

    /**
     * Test if the service is already loaded..
     * 
     * @param service
     *            the name of the service to test. Shouldn't be null.
     * @return true if the service is already loaded, false otherwise
     */
    public static boolean isPartAlreadyLoaded(final String service) {
        assert service != null : "parameter service shouldn't be null";

        return opBindingSharedInstance().isPartLoaded(service);
    }

    /**
     * Test if there is a view binded for such a service, resource, and action name.
     * 
     * @param service
     *            the service name for this view. Shouldn't be null.
     * @param resource
     *            the resource name for this view. Shouldn't be null.
     * @param action
     *            the action name for this view. Shouldn't be null.
     * @return true if there is a view binded for such a service, resource, and action name, false otherwise.
     */
    public static boolean isBinded(final String service, final String resource, final String action) {
        assert service != null : "parameter service shouldn't be null";
        assert resource != null : "parameter resource shouldn't be null";
        assert action != null : "parameter action should't be null";

        return opBindingSharedInstance().getAction(service, resource, action) != null;
    }

    /**
     * Return the url of the part loader js file for the given service name.
     * 
     * @param service
     *            the name of the service. Shouldn't be null.
     * @return the url for the part loader. Cannot be null.
     */
    public static String partLoaderUrlForService(final String service) {
        assert service != null : "parameter service shouldn't be null";

        return service + "/" + service + PART_LOADER_SUFFIX;
    }

    /**
     * Return the id used to tag the script containing the service part loader. Used to check if a script has already been loaded or not.
     * 
     * @param service
     *            the name of the service. Shouldn't be null.
     * @return the id for the script element containing the part loader. Cannot be null.
     */
    public static String partLoaderIdForService(final String service) {
        assert service != null : "parameter service shouldn't be null";

        return LOADED_SCRIPT_ID_PREFIX + service;
    }

    /**
     * Return the shared instance of the OPBinding. Also take care of basic initialization of the OPBinding.
     * 
     * @return the shared instance of the OPBinding. Cannot be null.
     */
    public static native OPBinding opBindingSharedInstance() /*-{
        if (! $wnd.op_binding) {
            $wnd.op_binding = @fr.openparts.OpenParts.client.OPBinding::bindingFactory()();
        }
        return  $wnd.op_binding;
    }-*/;

    /**
     * Load the part for the corresponding service. Doesn't do anything if the part is already loaded.
     * 
     * @param service
     *            the name of the service to load. Shouldn't be null.
     */
    public static void loadPart(final String service) {
        loadPart(service, null);
    }

    /**
     * Load the part for the corresponding service, and call the callback when it's done. Call directly the callback if the part is already loaded.
     * 
     * @param service
     *            the name of the service to load. Shouldn't be null.
     * @param callback
     *            the callback to call when the part is loaded and binded. Can be null.
     */
    public static void loadPart(final String service, final OPCommandCallback callback) {
        assert service != null : "parameter service shouldn't be null";

        if (isPartAlreadyLoaded(service)) {
            if (callback != null) {
                callback.onSuccess();
            }
            return;
        }

        final OPCommand command = OPCommandFactory.loadPartCommandFactory(service, callback);
        opBindingSharedInstance().scheduleCommand(command);
    }

    /**
     * Notify the part loader that the part with the given service name finished loading and binding itself. This method shouldn't be called directly, instead
     * the part should call the method {@link OPBinder#notifyLoadingCompleted()} at the very end of its loading method.
     * 
     * @param service
     *            the name of the service loaded. Shouldn't be null.
     */
    public static void notifyLoadingCompleted(final String service) {
        assert service != null : "parameter service shouldn't be null";

        opBindingSharedInstance().notifyLoadingCompleted(service);
    }

    /**
     * Dispatch the given event to all parts registered as listeners on the event bus.
     * 
     * @param event
     *            the event to dispatch. Shouldn't be null.
     */
    public static void dispatchEvent(final String event) {
        dispatchEvent(event, null, null);
    }

    /**
     * Dispatch the given event and parameters to all parts registered as listeners on the event bus.
     * 
     * @param event
     *            the event to dispatch. Shouldn't be null.
     * @param params
     *            the params associated with the event. Can be empty, can be null.
     */
    public static void dispatchEvent(final String event, final OPParams params) {
        dispatchEvent(event, params, null);
    }

    /**
     * Dispatch the given event to all parts registered as listeners on the event bus, and call the callback when it's done.
     * 
     * @param event
     *            the event to dispatch. Shouldn't be null.
     * @param callback
     *            the callback to call when the event is dispatched. Can be null.
     */
    public static void dispatchEvent(final String event, final OPCommandCallback callback) {
        dispatchEvent(event, null, callback);
    }

    /**
     * Dispatch the given event and parameters to all parts registered as listeners on the event bus, and call the callback when it's done.
     * 
     * @param event
     *            the event to dispatch. Shouldn't be null.
     * @param params
     *            the params associated with the event. Can be empty, can be null.
     * @param callback
     *            the callback to call when the event is dispatched. Can be null.
     */
    public static void dispatchEvent(final String event, final OPParams params, final OPCommandCallback callback) {
        assert event != null : "parameter event shouldn't be null";

        final OPCommand command = OPCommandFactory.dispatchEventCommandFactory(event, params);
        if (callback != null) {
            command.setCallback(callback);
        }

        opBindingSharedInstance().scheduleCommand(command);
    }

    /**
     * Dispatch the given event to a specific part registered as listener on the event bus. If the service is not already loaded, load it first. In this case,
     * the event will be dispatched asynchronously. If the service is already loaded, the event is dispatched immediately.
     * 
     * @param service
     *            the name of the service to which the event will be dispatched. Shouldn't be null.
     * @param event
     *            the event to dispatch. Shouldn't be null.
     */
    public static void dispatchEventTo(final String service, final String event) {
        dispatchEventTo(service, event, null, null);
    }

    /**
     * Dispatch the given event and parameters to a specific part registered as listener on the event bus. If the service is not already loaded, load it first.
     * In this case, the event will be dispatched asynchronously. If the service is already loaded, the event is dispatched immediately.
     * 
     * @param service
     *            the name of the service to which the event will be dispatched. Shouldn't be null.
     * @param event
     *            the event to dispatch. Shouldn't be null.
     * @param params
     *            the params associated with the event. Can be empty, can be null.
     */
    public static void dispatchEventTo(final String service, final String event, final OPParams params) {
        dispatchEventTo(service, event, params, null);
    }

    /**
     * Dispatch the given event to a specific part registered as listener on the event bus, and call the callback when it's done. If the service is not already
     * loaded, load it first. In this case, the event will be dispatched asynchronously. If the service is already loaded, the event is dispatched immediately.
     * 
     * @param service
     *            the name of the service to which the event will be dispatched. Shouldn't be null.
     * @param event
     *            the event to dispatch. Shouldn't be null.
     * @param callback
     *            the callback to call when the event is dispatched. Can be null.
     */
    public static void dispatchEventTo(final String service, final String event, final OPCommandCallback callback) {
        dispatchEventTo(service, event, null, callback);
    }

    /**
     * Dispatch the given event and parameters to a specific part registered as listener on the event bus, and call the callback when it's done. If the service
     * is not already loaded, load it first. In this case, the event will be dispatched asynchronously. If the service is already loaded, the event is
     * dispatched immediately.
     * 
     * @param service
     *            the name of the service to which the event will be dispatched. Shouldn't be null.
     * @param event
     *            the event to dispatch. Shouldn't be null.
     * @param params
     *            the params associated with the event. Can be empty, can be null.
     * @param callback
     *            the callback to call when the event is dispatched. Can be null.
     */
    public static void dispatchEventTo(final String service, final String event, final OPParams params, final OPCommandCallback callback) {
        assert service != null : "parameter service shouldn't be null";
        assert event != null : "parameter event shouldn't be null";

        final OPCommand command = OPCommandFactory.dispatchEventToCommandFactory(service, event, params);
        if (callback != null) {
            command.setCallback(callback);
        }

        opBindingSharedInstance().scheduleCommand(command);
    }

    /**
     * Returns a unique slot id for the given view. It is always unique, not only between parts but also each time the method is called. The goal is to provide
     * a slot that can be used to display several instances of the same view in the same page. It is build from the service name, the resource name, the action
     * name, and a unique counter incremented by each call for all views.
     * 
     * @param service
     *            the service name for this view. Shouldn't be null.
     * @param resource
     *            the resource name for this view. Shouldn't be null.
     * @param action
     *            the action name for this view. Shouldn't be null.
     * @return a unique slot id for this view. Cannot be null.
     */
    public static String getUniqueSlotIdFor(final String service, final String resource, final String action) {
        assert service != null : "parameter service shouldn't be null";
        assert resource != null : "parameter resource shouldn't be null";
        assert action != null : "parameter action shouldn't be null";

        return "OP-" + service + "-" + resource + "-" + action + opBindingSharedInstance().getGlobalSlotCounter();
    }

    /**
     * Returns a slot with the given slotId.
     * 
     * @param slotId
     *            the given slotId. Shouldn't be null. Shouldn't be empty.
     * @return the slot with the given slotId. Cannot be null.
     */
    public static OPSlot getSlot(final String slotId) {
        assert slotId != null : "parameter slotId shouldn't be null";
        assert !slotId.isEmpty() : "parameter slotId shouldn't be empty";

        return new OPSlot(slotId);
    }

    /**
     * Returns a slot for the given view.
     * 
     * @param service
     *            the service name for this view. Shouldn't be null.
     * @param resource
     *            the resource name for this view. Shouldn't be null.
     * @param action
     *            the action name for this view. Shouldn't be null.
     * @return the slot for the given view. Cannot be null.
     */
    public static OPSlot getSlot(final String service, final String resource, final String action) {
        assert service != null : "parameter service shouldn't be null";
        assert resource != null : "parameter resource shouldn't be null";
        assert action != null : "parameter action shouldn't be null";

        return new OPSlot(getUniqueSlotIdFor(service, resource, action));
    }

    /**
     * Display the view for the corresponding service part, for the corresponding resource managed by this part, and for the corresponding action. Parameters
     * are passed as an hash to the view, in order to initialize itself. The view will display itself as the root panel of the page, and call the callback when it's done.
     * <p>
     * The view can be displayed only once in the page. Calling this method two times on the same view for the same page will replace the first view with the
     * second.
     * <p>
     * If the service is not already loaded, load it first. In this case, the view will be displayed asynchronously. If the service is already loaded, the view
     * is displayed immediately.
     * 
     * @param service
     *            the service part name. Shouldn't be null.
     * @param resource
     *            the resource type managed by this part. Shouldn't be null.
     * @param action
     *            the action corresponding to the view to display. Shouldn't be null.
     * @param params
     *            hash of parameters passed to the view for its initialization. Can be empty, can be null.
     * @param callback
     *            the callback to call when the view is displayed. Can be null.
     */
    public static void displayViewAsRoot(final String service, final String resource, final String action, final OPParams params, final OPCommandCallback callback) {
        displayView(service, resource, action, SLOT_ID_ROOT, params, callback);
    }

    /**
     * Display the view for the corresponding service part, for the corresponding resource managed by this part, and for the corresponding action. The view will
     * display itself in the given slot.
     * <p>
     * The view can be displayed only once in the page. Calling this method two times on the same view for the same page will replace the first view with the
     * second.
     * <p>
     * If the service is not already loaded, load it first. In this case, the view will be displayed asynchronously. If the service is already loaded, the view
     * is displayed immediately.
     * 
     * @param service
     *            the service part name. Shouldn't be null.
     * @param resource
     *            the resource type managed by this part. Shouldn't be null.
     * @param action
     *            the action corresponding to the view to display. Shouldn't be null.
     * @param slot
     *            the slot where the view will be displayed. Shouldn't be null.
     */
    public static void displayView(final String service, final String resource, final String action, final OPSlot slot) {
        displayView(service, resource, action, slot, null, null);
    }

    /**
     * Display the view for the corresponding service part, for the corresponding resource managed by this part, and for the corresponding action. Parameters
     * are passed as an hash to the view, in order to initialize itself. The view will display itself in the given slot.
     * <p>
     * The view can be displayed only once in the page. Calling this method two times on the same view for the same page will replace the first view with the
     * second.
     * <p>
     * If the service is not already loaded, load it first. In this case, the view will be displayed asynchronously. If the service is already loaded, the view
     * is displayed immediately.
     * 
     * @param service
     *            the service part name. Shouldn't be null.
     * @param resource
     *            the resource type managed by this part. Shouldn't be null.
     * @param action
     *            the action corresponding to the view to display. Shouldn't be null.
     * @param params
     *            hash of parameters passed to the view for its initialization. Can be empty, can be null.
     * @param slot
     *            the slot where the view will be displayed. Shouldn't be null.
     */
    public static void displayView(final String service, final String resource, final String action, final OPSlot slot, final OPParams params) {
        displayView(service, resource, action, slot, params, null);
    }

    /**
     * Display the view for the corresponding service part, for the corresponding resource managed by this part, and for the corresponding action. The view will
     * display itself in the given slot, and call the callback when it's done.
     * <p>
     * The view can be displayed only once in the page. Calling this method two times on the same view for the same page will replace the first view with the
     * second.
     * <p>
     * If the service is not already loaded, load it first. In this case, the view will be displayed asynchronously. If the service is already loaded, the view
     * is displayed immediately.
     * 
     * @param service
     *            the service part name. Shouldn't be null.
     * @param resource
     *            the resource type managed by this part. Shouldn't be null.
     * @param action
     *            the action corresponding to the view to display. Shouldn't be null.
     * @param slot
     *            the slot where the view will be displayed. Shouldn't be null.
     * @param callback
     *            the callback to call when the view is displayed. Can be null.
     */
    public static void displayView(final String service, final String resource, final String action, final OPSlot slot, final OPCommandCallback callback) {
        displayView(service, resource, action, slot, null, callback);
    }

    /**
     * Display the view for the corresponding service part, for the corresponding resource managed by this part, and for the corresponding action. Parameters
     * are passed as an hash to the view, in order to initialize itself. The view will display itself in the given slot, and call the callback when it's done.
     * <p>
     * The view can be displayed only once in the page. Calling this method two times on the same view for the same page will replace the first view with the
     * second.
     * <p>
     * If the service is not already loaded, load it first. In this case, the view will be displayed asynchronously. If the service is already loaded, the view
     * is displayed immediately.
     * 
     * @param service
     *            the service part name. Shouldn't be null.
     * @param resource
     *            the resource type managed by this part. Shouldn't be null.
     * @param action
     *            the action corresponding to the view to display. Shouldn't be null.
     * @param slot
     *            the slot where the view will be displayed. Shouldn't be null.
     * @param params
     *            hash of parameters passed to the view for its initialization. Can be empty, can be null.
     * @param callback
     *            the callback to call when the view is displayed. Can be null.
     */
    public static void displayView(final String service, final String resource, final String action, final OPSlot slot, final OPParams params,
            final OPCommandCallback callback) {
        assert slot != null : "parameter slot shouldn't be null";

        displayView(service, resource, action, slot.getSlotId(), params, callback);
    }

    /**
     * Display the view for the corresponding service part, for the corresponding resource managed by this part, and for the corresponding action. The view will
     * display itself in the given slot id.
     * <p>
     * The view can be displayed only once in the page. Calling this method two times on the same view for the same page will replace the first view with the
     * second.
     * <p>
     * If the service is not already loaded, load it first. In this case, the view will be displayed asynchronously. If the service is already loaded, the view
     * is displayed immediately.
     * 
     * @param service
     *            the service part name. Shouldn't be null.
     * @param resource
     *            the resource type managed by this part. Shouldn't be null.
     * @param action
     *            the action corresponding to the view to display. Shouldn't be null.
     * @param slotId
     *            the slot id where the view will be displayed. Shouldn't be null. Shouldn't be empty.
     */
    public static void displayView(final String service, final String resource, final String action, final String slotId) {
        displayView(service, resource, action, slotId, null, null);
    }

    /**
     * Display the view for the corresponding service part, for the corresponding resource managed by this part, and for the corresponding action. Parameters
     * are passed as an hash to the view, in order to initialize itself. The view will display itself in the given slot id.
     * <p>
     * The view can be displayed only once in the page. Calling this method two times on the same view for the same page will replace the first view with the
     * second.
     * <p>
     * If the service is not already loaded, load it first. In this case, the view will be displayed asynchronously. If the service is already loaded, the view
     * is displayed immediately.
     * 
     * @param service
     *            the service part name. Shouldn't be null.
     * @param resource
     *            the resource type managed by this part. Shouldn't be null.
     * @param action
     *            the action corresponding to the view to display. Shouldn't be null.
     * @param params
     *            hash of parameters passed to the view for its initialization. Can be empty, can be null.
     * @param slotId
     *            the slot id where the view will be displayed. Shouldn't be null. Shouldn't be empty.
     */
    public static void displayView(final String service, final String resource, final String action, final String slotId, final OPParams params) {
        displayView(service, resource, action, slotId, params, null);
    }

    /**
     * Display the view for the corresponding service part, for the corresponding resource managed by this part, and for the corresponding action. The view will
     * display itself in the given slot id, and call the callback when it's done.
     * <p>
     * The view can be displayed only once in the page. Calling this method two times on the same view for the same page will replace the first view with the
     * second.
     * <p>
     * If the service is not already loaded, load it first. In this case, the view will be displayed asynchronously. If the service is already loaded, the view
     * is displayed immediately.
     * 
     * @param service
     *            the service part name. Shouldn't be null.
     * @param resource
     *            the resource type managed by this part. Shouldn't be null.
     * @param action
     *            the action corresponding to the view to display. Shouldn't be null.
     * @param slotId
     *            the slot id where the view will be displayed. Shouldn't be null. Shouldn't be empty.
     * @param callback
     *            the callback to call when the view is displayed. Can be null.
     */
    public static void displayView(final String service, final String resource, final String action, final String slotId, final OPCommandCallback callback) {
        displayView(service, resource, action, slotId, null, callback);
    }

    /**
     * Display the view for the corresponding service part, for the corresponding resource managed by this part, and for the corresponding action. Parameters
     * are passed as an hash to the view, in order to initialize itself. The view will display itself in the given slot id, and call the callback when it's
     * done.
     * <p>
     * The view can be displayed only once in the page. Calling this method two times on the same view for the same page will replace the first view with the
     * second.
     * <p>
     * If the service is not already loaded, load it first. In this case, the view will be displayed asynchronously. If the service is already loaded, the view
     * is displayed immediately.
     * 
     * @param service
     *            the service part name. Shouldn't be null.
     * @param resource
     *            the resource type managed by this part. Shouldn't be null.
     * @param action
     *            the action corresponding to the view to display. Shouldn't be null.
     * @param slotId
     *            the slot id where the view will be displayed. Shouldn't be null. Shouldn't be empty.
     * @param params
     *            hash of parameters passed to the view for its initialization. Can be empty, can be null.
     * @param callback
     *            the callback to call when the view is displayed. Can be null.
     */
    public static void displayView(final String service, final String resource, final String action, final String slotId, final OPParams params,
            final OPCommandCallback callback) {
        assert service != null : "parameter service shouldn't be null";
        assert resource != null : "parameter resource shouldn't be null";
        assert action != null : "parameter action shouldn't be null";
        assert slotId != null : "parameter slotId shouldn't be null";
        assert !slotId.isEmpty() : "parameter slotId shouldn't be empty";

        final OPCommand command = OPCommandFactory.displayViewCommandFactory(service, resource, action, params, slotId);
        if (callback != null) {
            command.setCallback(callback);
        }
        
        opBindingSharedInstance().scheduleCommand(command);
    }

}
